说一下 select、poll、epoll

10 分钟阅读 1142 字 + 639 词
总结:
I/O多路复用通常通过select、poll、epoll等系统调用来实现。
  • select: select是一个 最古老的I/O多路复用机制 ,它可以监视多个文件描述符的可读、可写和错误状态。然而,但是它的效率可能随着监视的文件描述符数量的增加而降低。因为select使用 fd_set位图 来进行监听, 将需要监听的文件描述符放入这个fd_set中 ,让 内核来检查是否有⽹络事件产⽣ ,检查的⽅式很粗暴,就是通过 遍历⽂件描述符集合的⽅式 ,当 检查到有事件产⽣后 ,将 此 Socket 标记为可读或可写 , 接着再把 整个⽂件描述符集合拷⻉回⽤户态 ⾥,然后**⽤户态 还需要再 通过遍历的⽅法(每个判断if ISSET)找到可读或可写的 Socket**,然后再对其处理。所以, 对于 select 这种⽅式,需要进⾏ 2 次「遍历」⽂件描述符集合,⼀次是在内核态⾥,⼀次是在⽤户态⾥ ,⽽且还会发⽣ 2 次「拷⻉」⽂件描述符集合,先从⽤户空间传⼊内核空间,由内核修改后,再传出到⽤户空间中。fd_set的固定大小是1024 ,所以监听的数量有限,并且 监听和就绪是耦合 的。
  • poll: poll是select的一种改进,它使用 轮询方式 来检查多个文件描述符的状态, 避免了select中文件描述符数量有限的问题,不再使用位图 ,而是 使用动态数组,以链表的形式来组织 。监听和返回的集合也进行了分离,但对于大量的文件描述符,poll的性能也可能变得不足够高效,因为 需要全部遍历所有的文件描述符
    • poll比起select,改进了文件描述数量有限制的问题,使用链表来组织文件描述符,并且对监听和返回的集合也进行了分离。但是还是需要遍历全部的文件描述符检查可读可写
  • epoll:
    • epoll是Linux特有的I/O多路复用机制,相较于select和poll,它在处理大量文件描述符时更加高效 。epoll 使用事件通知的方式 只有在文件描述符就绪时才会通知应用程序 ,而 不需要轮询 。并且 监听集合 采用 红黑树 ,不用像select那样每次操作都传入整个集合,而是传入检测的fd就行,大小无限制,并且 监听和就绪分离 只需遍历就绪集合即可,不用遍历所有的文件描述符
    • epoll 使⽤ 事件驱动的机制 内核⾥维护了⼀个链表来记录就绪事件 ,当某个socket 有事件发⽣时,通过 回调函数内核 会将其加⼊到这个 就绪事件链表 中,当**⽤户调⽤epoll_wait() 函 数时,只会 返回有事件发⽣的⽂件描述符的个数**,不需要像 select/poll 那样轮询扫描整个 socket 集合,⼤⼤提⾼了检测的效率。
image-20240920171041625
select/poll
select 实现多路复⽤的⽅式是,将已连接的 Socket 都放到⼀个⽂件描述符集合,然后调⽤select 函数将⽂件描述符集合拷⻉到内核⾥,让内核来检查是否有⽹络事件产⽣,检查的⽅式很粗暴,就是通过遍历⽂件描述符集合的⽅式,当检查到有事件产⽣后,将此 Socket 标记为可读或可写, 接着再把整个⽂件描述符集合拷⻉回⽤户态⾥,然后⽤户态还需要再通过遍历的⽅法找到可读或可写的 Socket,然后再对其处理。所以, 对于 select 这种⽅式,需要进⾏ 2 次「遍历」⽂件描述符集合,⼀次是在内核态⾥,⼀个次是在⽤户态⾥ ,⽽且还会发⽣ 2 次「拷⻉」⽂件描述符集合,先从⽤户空间传⼊内核空间,由内核修改后,再传出到⽤户空间中。 select 使⽤固定⻓度的 BitsMap,表示⽂件描述符集合,⽽且所⽀持的⽂件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最⼤值为 1024 ,只能监听 0~1023 的⽂件描述符。
poll 不再⽤ BitsMap 来存储所关注的⽂件描述符,取⽽代之⽤动态数组,以链表形式来组织,突破了 select 的⽂件描述符个数限制,当然还会受到系统⽂件描述符限制。但是 poll 和 select 并没有太⼤的本质区别, 都是使⽤「线性结构」存储进程关注的 Socket集合,因此都需要遍历⽂件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),⽽且也需要在⽤户态与内核态之间拷⻉⽂件描述符集合,这种⽅式随着并发数上来,性能的损耗会呈指数级增⻓。